#include "BTree.h"
#include "NodoDatos.h"
#include "NodoIndice.h"
#include "RegistroIndice.h"
#include "RegistroComprimidoDatos.h"


BTree::BTree() : _nSecuencial(NULL){
	// No hace nada
}


BTree::BTree(std::string nomArch, bool crear) : _nSecuencial(NULL)
{
	if (!crear){
		// Abre el archivo, si no existe tira un error
		bool exito = this->abrir(nomArch);

		if (!exito)
			throw std::runtime_error("Error al abrir el árbol");

	} else {	// Crea un archivo nuevo

		bool exito = this->crear(nomArch);
		if (!exito)
			throw std::runtime_error("Error al crear el árbol");
	}
}


bool BTree::abrir(std::string nomArch){
	if (this->_arch.is_open()){ // Cierra el archivo

		// Guarda los datos y limpia todo
		this->_arch.guardar(this->_cabecera, 0);

		if (this->_nSecuencial != NULL){
			delete this->_nSecuencial;
			this->_nSecuencial = NULL;
		}

		this->_arch.cerrar();
	}

	// Trata de abrir el archivo
	bool exito = this->_arch.abrirArch(this->_cabecera, nomArch);
	return exito;
}


bool BTree::crear(std::string nomArch){

	// Primero hay que inicializar la cabecera
	this->_cabecera.setAlturaArbol(0);
	this->_cabecera.setCantBloques(2);	// Solo la cabecera y la raiz
	this->_cabecera.setPosBloqueRaiz(1);
	this->_cabecera.setPosBloqueSecuencial(1);

	bool exito = this->_arch.crearArch(this->_cabecera, nomArch);
	if (!exito) return exito;

	// Guarda una raiz vacia
	NodoDatos nd;
	int p = this->_arch.guardar(nd, this->_cabecera.getPosBloqueRaiz());

	// Un chequeo de errores simple
	if (p == -1){
		return false;
	} else {
		return true;
	}
}


BTree::~BTree(){
	cerrar();
}


void BTree::cerrar(){
	if (this->_arch.is_open()){

		// Guarda la cabecera
		this->_arch.guardar(this->_cabecera, 0);

		if (this->_nSecuencial != NULL){
			delete this->_nSecuencial;
			this->_nSecuencial = NULL;
		}
	}
}


RegistroDatos BTree::buscarRegistro(RegistroDatos regBuscar) {
	// Carga la raiz
	INodo* raiz = cargarNodo(this->_cabecera.getPosBloqueRaiz(), this->_cabecera.getAlturaArbol());

	// El registro encontrado
	RegistroDatos reg;
	if ( this->_cabecera.getAlturaArbol() == 0) {
		reg = raiz->buscarRegistroPorId(regBuscar, true);

	} else {
		reg = buscarRecursivo(regBuscar, raiz, this->_cabecera.getAlturaArbol()-1);
	}

	delete raiz;
	return reg;
}


RegistroDatos BTree::buscarRecursivo(RegistroDatos regId, INodo* nodo, int altura){

	RegistroIndice rIndex = nodo->buscarRegistroPorId(regId, false);
	INodo* n = cargarNodo(rIndex.getPosHijo(), altura);

	RegistroDatos rd;
	if (altura == 0){
		rd = n->buscarRegistroPorId(regId, true);
	} else {
		rd = buscarRecursivo(regId, n, altura-1);
	}

	delete n;
	return rd;
}


Resultado BTree::insertarRegistro(RegistroDatos* reg){
	// Carga la raiz del árbol
	INodo* raiz = cargarNodo(this->_cabecera.getPosBloqueRaiz(), this->_cabecera.getAlturaArbol());

	// Se guardan los registros comprimidos
	RegistroComprimidoDatos* regComprimido = new RegistroComprimidoDatos(*reg);

	Resultado resul;
	if (this->_cabecera.getAlturaArbol() == 0){
		resul = raiz->agregarRegistro(regComprimido);
	} else {
		resul = insertarRegistroRecursivo(regComprimido, raiz, this->_cabecera.getAlturaArbol()-1);
	}

	// Manejar el resultado de la inserción
	if (resul == MODIFICADO || resul == SUB_FLUJO){
		// Guarda la raiz modificada y la cabecera actualizada
		guardarNodo(raiz, this->_cabecera.getPosBloqueRaiz());
		this->_arch.guardar(this->_cabecera, 0);
		resul = OK;

	} else if (resul == SOBRE_FLUJO){
		// Se crea una nueva raiz y se inserta un registro con datos invalidos para que funcione
		// el algoritmo de resolver overflow
		INodo* newRaiz = new NodoIndice();
		RegistroIndice* rRaiz = new RegistroIndice(IRegistro::GET_MAXIMO_IMPOSIBLE(), -1);
		newRaiz->agregarRegistro(rRaiz);

		// Resuelve el overflow de la raiz
		resolverOverflow(raiz, newRaiz, *rRaiz, this->_cabecera.getAlturaArbol());

		// Se guarda la nueva raiz
		guardarNodo(newRaiz, this->_cabecera.getPosBloqueRaiz());

		actualizarCabecera(newRaiz);

		delete newRaiz;
		resul = OK;
	}

	// Limpia los registros necesarios según el resultado
	if (resul == ERROR || resul == CLAVE_DUPLICADA){
		// No se insertó el registro comprimido, hay que borrarlo
		// El registro que pasó el usuario es cosa suya, tiene que ver que hace según el resultado devuelto por el metodo
		delete regComprimido;

	} else {
		// El resto de los resultados implican que se insertó el registro comprimido
		// Para mantener la interfaz consistente hay que eliminar el registro que pasó el usuario
		delete reg;
	}

	delete raiz;

	// Hubo cambios en el árbol, hay que re-setear el nodo secuencial
	if (this->_nSecuencial != NULL){
		delete this->_nSecuencial;
		this->_nSecuencial = NULL;
	}

	return resul;
}

Resultado BTree::insertarRegistroRecursivo(RegistroDatos* reg, INodo* nPadre, int altura){

	// Busca el registro y carga el nodo apuntado por el mismo
	RegistroIndice r = nPadre->buscarRegistroPorId(*reg, false);
	INodo* nActual = cargarNodo(r.getPosHijo(), altura);

	Resultado resul;
	if (altura == 0){
		resul = nActual->agregarRegistro(reg);
	} else {
		resul = insertarRegistroRecursivo(reg, nActual, altura-1);
	}

	// Maneja el resultado de la inserción
	if (resul == MODIFICADO){
		guardarNodo(nActual, r.getPosHijo());
		resul = OK;

	} else if (resul == SOBRE_FLUJO){
		resul = resolverOverflow(nActual, nPadre, r, altura);
	}

	delete nActual;
	return resul;
}


// Actualiza la altura del árbol y la referencia al primer nodo de datos
void BTree::actualizarCabecera(INodo* newRaiz){

	// Si la altura del arbol era "0" el primer bloque de datos cambiará de posicion
	if (this->_cabecera.getAlturaArbol() == 0) {

		// Hay que buscar la posicion del primer nodo de datos
		RegistroIndice r("", -1);	// No puede existir un registro con clave menor
		r = newRaiz->buscarRegistroPorId(r, false);
		this->_cabecera.setPosBloqueSecuencial(r.getPosHijo());

		// El primer bloque de datos no volverá a cambiar de posición
	}

	// Aumenta la altura del árbol
	this->_cabecera.setAlturaArbol(this->_cabecera.getAlturaArbol()+1);

	// Guarda los datos de la cabecera para mantenerla actualizada
	this->_arch.guardar(this->_cabecera, 0);
}


// Lee desde el disco el nodo en la posicion correspondiente
INodo* BTree::cargarNodo(int posBloque, int altura){

	// Según la altura donde nos encontramos leyendo es el tipo de nodo
	INodo* n;
	if (altura == 0) n = new NodoDatos();
	else n = new NodoIndice();

	// Lee el nodo desde el archivo
	this->_arch.leer(*n, posBloque);

	return n;
}


// Guarda el nodo en el archivo, si no se indica posición se guarda en un bloque libre
// o uno nuevo si es que no hay libres, devuelve la posición donde se guardó
int BTree::guardarNodo(INodo* n, int posBloque){
	if (posBloque == -1){
		// Se esta agregando un nuevo bloque
		this->_cabecera.setCantBloques(this->_cabecera.getCantBloques()+1);
	}
	return this->_arch.guardar(*n, posBloque);
}


// Resuelve la situación de sobreflujo de los nodos del árbol
Resultado BTree::resolverOverflow(INodo* nHijo, INodo* nPadre, RegistroIndice rPadre, int altura){

	INodo* nNuevo;
	if (altura == 0) nNuevo = new NodoDatos();
	else nNuevo = new NodoIndice();

	// El registro que devuelve el split contiene el separdor entre "nHijo" y "nNuevo"
	IRegistro* rClave = nHijo->splitNodo(*nNuevo);

	// Si es un overflow de hojas hay que enlazar los nodos
	if (altura == 0){
		int pos = static_cast<NodoDatos*>(nHijo)->getNextNodo();
		static_cast<NodoDatos*>(nNuevo)->setNextNodo(pos);
	}

	// Guarda el nuevo nodo
	int posNuevo = guardarNodo(nNuevo);
	delete nNuevo;	// Ya no necesito el nuevo nodo

	// Si es un overflow de hojas hay que enlazar los nodos
	if (altura == 0){
		static_cast<NodoDatos*>(nHijo)->setNextNodo(posNuevo);
	}

	// Guarda el nodo actualizado,
	// Se guarda la "pos" por si el guardar resulta en un nuevo bloque
	int posHijo = guardarNodo(nHijo, rPadre.getPosHijo());

	// Los nuevos registros que hay que agregar al padre
	IRegistro* r1 = new RegistroIndice(rClave->getClave(), posHijo);
	IRegistro* r2 = new RegistroIndice(rPadre.getClave(), posNuevo);

	// Quita la anterior clave maxima y guarda las nuevas
	nPadre->modificarRegistroPorId(r2);
	Resultado resul = nPadre->agregarRegistro(r1);

	// Devuelve el resultado de la ultima operación de inserción por si hay que informar algo
	delete rClave;
	return resul;
}


// Devuelve una copia del registro de datos con menor Id del árbol
RegistroDatos BTree::proximoRegistroPorId(){
	if (this->_nSecuencial != NULL){
		// Ya existe un nodo cargado, hay que eliminarlo
		delete this->_nSecuencial;
		this->_nSecuencial = NULL;
	}

	// Carga el primer nodo de datos
	int pos = this->_cabecera.getPosBloqueSecuencial();
	this->_nSecuencial = static_cast<NodoDatos*>( cargarNodo(pos, 0) );

	// Un registro vacio deberia la menor clave imposible
	RegistroDatos r;
	return this->_nSecuencial->buscarRegistroPorId(r, false);
}


// Devuelve una copia del proximo registro con id mayor a "regID"
RegistroDatos BTree::proximoRegistroPorId(const RegistroDatos regID){
	if (this->_nSecuencial == NULL){
		this->_nSecuencial = buscarNodoPorRegistro(regID, this->_cabecera.getPosBloqueRaiz(),
				this->_cabecera.getAlturaArbol());
	}

	// La busqueda inexacta trae el mayor inmediato
	RegistroDatos rd = this->_nSecuencial->buscarRegistroPorId(regID, false);

	// Si el encontrado es mayor que el buscado lo devuelvo
	if (rd.getClave() > regID.getClave()){
		return rd;
	}

	// Si el encontrado no es mayor que el buscado hay que probar en la siguiente hoja
	int posProx = this->_nSecuencial->getNextNodo();
	if (posProx == -1){
		// Se llegó al final hay que resetear todo
		delete this->_nSecuencial;
		this->_nSecuencial = NULL;

		return rd;

	} else if (posProx >= this->_cabecera.getCantBloques()){
		// Para chequear un bug que un dia ví y se arregló solo
		throw std::range_error("El puntero al próximo nodo hoja esta fuera del rango de la cantidad de nodos existentes");
	}

	// Carga el proximo nodo y busca el registro
	delete this->_nSecuencial;
	this->_nSecuencial = static_cast<NodoDatos*>( cargarNodo(posProx, 0) );
	return this->_nSecuencial->buscarRegistroPorId(regID, false);
}



// Devuelve el nodo de datos donde se debe encontrar el registro
const NodoDatos* BTree::buscarNodoPorRegistro(const RegistroDatos& reg, int posNodo, int altura){
	// Es muy parecido a la busqueda de registros recursivos, pero este devuelve el nodo y no el registro

	const INodo* nodo = cargarNodo(posNodo, altura);

	if (altura != 0) {
		const RegistroIndice r = nodo->buscarRegistroPorId(reg);
		delete nodo;

		nodo = buscarNodoPorRegistro(reg, r.getPosHijo(), altura-1);
	}

	return static_cast<const NodoDatos*>(nodo);
}


// Deja "nDatos" apuntando al nodo que contiene el registro señalado y devuelve la posicion del bloque en disco
int BTree::recuperarNodoPorRegistro(const RegistroDatos& reg, int posNodo, int altura, NodoDatos* &nDatos){
	INodo* nodo = cargarNodo(posNodo, altura);

	if (altura == 0){
		// Devuelve el nodo y su posicion
		nDatos = static_cast<NodoDatos*>(nodo);
		return posNodo;
	}

	// Busca la posicion del nodo hijo donde seguir la busqueda recursiva
	RegistroIndice ri = nodo->buscarRegistroPorId(reg, false);
	delete nodo;

	return recuperarNodoPorRegistro(reg, ri.getPosHijo(), altura-1, nDatos);
}


Resultado BTree::modificarRegistro(RegistroDatos* reg){
	// Devuelve el nodo donde se encuentra el registro buscado
	NodoDatos* n = NULL;

	// Recupera el nodo hoja que contiene al registro a modificar
	int pos = recuperarNodoPorRegistro(*reg, this->_cabecera.getPosBloqueRaiz(), this->_cabecera.getAlturaArbol(), n);
	if (n == NULL) throw std::runtime_error("No se recuperó el nodo de datos");

	// El nodo de datos del árbol guarda registros comprimidos
	RegistroComprimidoDatos* rComp = new RegistroComprimidoDatos(*reg);

	// Modifica el registro y guarda el nodo
	Resultado res = n->modificarRegistroPorId(rComp);
	if (res == NO_ENCONTRADO){
		delete rComp;

	} else if (res == MODIFICADO || res == SUB_FLUJO) {
		guardarNodo(n, pos);
		delete reg;

	} else {
		// TODO: Resolver este caso especial de sobre-flujo
		if (res == SOBRE_FLUJO) throw std::runtime_error("Tu modificación genero un sobreFlujo, esto no esta contemplado por ahora");
		else throw std::runtime_error("Un resultado no tenido en cuenta");
	}

	delete n;
	return res;
}


bool BTree::is_open() const {
	return this->_arch.is_open();
}
